<?php

// Namespace

namespace Loop;


/**
 * CApplicationFactory takes care of the whole deployment of an application on a web server
 * It offers a wide range of APIs to extract, build and deploy on various servers
 *
 * @package		Loop
 * @category	Factory
 */

class CApplicationFactory
{
	/**
	 *
	 */
	
	public function api_build()
	{
		// Call the method
		
		$result = $this->build();
		
		
		return $result;
	}
	
	
	/**
	 *
	 */
	
	public function api_clean()
	{
		// Get API arguments

		$exportBackup = fv('exportBackup', false, true);
		$extensionsIds = json_decode(fv('extensionsIds', false, '[]'));


		// Call the method
		
		$this->clean($exportBackup, $extensionsIds);
	}


	/**
	 *
	 */
	
	public function api_export()
	{
		// Get API arguments

		$extensionsIds = json_decode(fv('extensionsIds', false, '[]'));
		
		
		// Call the method
		
		$this->export($extensionsIds);
	}
	
	
	/**
	 *
	 */
	
	public function api_forge()
	{
		// Get API arguments

		$exportBackup = fv('exportBackup', false, true);
		$extensionsIds = json_decode(fv('extensionsIds', false, '[]'));


		// Call the method
		
		$this->forge($exportBackup, $extensionsIds);
	}


	/**
	 *
	 */
	
	public function api_import()
	{
		// Get API arguments
		
		$dateOfBackup = fv('dateOfBackup', true);
		$extensionsIds = json_decode(fv('extensionsIds', false, '[]'));

		
		// Call the method
		
		$this->import($dateOfBackup, $extensionsIds);
	}
	
	
	/**
	 *
	 */
	
	public function api_install()
	{
		// Get API arguments
		
		$extensionsIds = json_decode(fv('extensionsIds', false, '[]'));


		// Call the method
		
		$this->install($extensionsIds);
	}
	
	
	/**
	 *
	 */
	
	public function api_startMaintenance()
	{
		// Get API arguments
		
		$maintenanceKey = fv('maintenanceKey', false);
		
		
		// Call the method
		
		$result = $this->startMaintenance($maintenanceKey);
		
		
		return $result;
	}
	
	
	/**
	 *
	 */
	
	public function api_stopMaintenance()
	{
		// Get API arguments
		
		$maintenanceKey = fv('maintenanceKey', false);
		
		
		// Call the method
		
		$result = $this->stopMaintenance($maintenanceKey);
		
		
		return $result;
	}
	
	
	/**
	 *
	 */
	
	public function api_test()
	{
		// Get API arguments
		
		$testName = fv('testName', false);
		$fullTest = fv('fullTest', false);
		
		
		// Call the method
		
		$result = $this->test($testName, $fullTest);
		
		
		return $result;
	}


	/**
	 *
	 */
	
	public function build()
	{
		// Get all extensions

		$extensionsIds = app()->getExtensionManager()->getExtensionsIds();


		// Get the latest date of backup of each extension

		foreach ($extensionsIds as $extensionId)
		{
			$dateOfBackup = $this->getLastDateOfBackup($extensionId);

			if (empty($dateOfBackup) === false)
			{
				$datesOfBackup[$extensionId] = $dateOfBackup;
			}
		}


		// Forge all extensions

		$this->forge($extensionsIds);


		// Import all backups found before the forge

		foreach ($extensionsIds as $extensionId)
		{
			if (isset($datesOfBackup[$extensionId]) === true)
			{
				$this->import($dateOfBackup, array($extensionId));
			}
		}
	}
	
	
	/**
	 *
	 */
	
	public function clean($exportBackup = true, $extensionsIds = null)
	{
		// Make sure we have extensions

		if (empty($extensionsIds) == true)
		{
			$extensionsIds = app()->getExtensionManager()->getExtensionsIds();
		}


		// Make sure we have a backup

		if ($exportBackup == true)
		{
			$pathToBackup = $this->export($extensionsIds);
		}


		// Clean each extension

		foreach	($extensionsIds as $extensionId)
		{
			// Skip shared extensions

			if (pref('[' . $extensionId . ']/database/isShared') == true)
			{
				continue;
			}


			// Get all tables of the extension

			$tablesNames = db($extensionId)->listTables();


			// Remove the content of each table
			
			foreach ($tablesNames as $tableName)
			{
				$queryVariables = array
				(
					pref('[' . $extensionId . ']/database/name'), 
					$tableName
				);

				$query = 'TRUNCATE TABLE `%1`.`%2`';

				db($extensionId)->executeQuery($query, $queryVariables);

			}
		}
	}


	/**
	 *
	 */
	
	public function export($extensionsIds = null)
	{
		// Make sure we have extensions

		if (empty($extensionsIds) == true)
		{
			$extensionsIds = app()->getExtensionManager()->getExtensionsIds();
		}


		// Export each extension

		foreach ($extensionsIds as $extensionId)
		{
			// Ignore shared extensions

			if (pref('[' . $extensionId . ']/database/isShared') == true)
			{
				continue;
			}


			// Ensure the path to backup exists

			$pathToBackup = path
			(
				array
				(
					app()->getPathHelper()->convertExtensionIdToPath($extensionId),
					'Data',
					'Backup'
				),
				true
			);


			// Setup the filename

			$filename = 'Backup_' . app()->getTimeHelper()->convertTimestampToDate(time(), null, 'Y-m-d_H-i-s') . '.sql';
			$pathToFile = $pathToBackup . $filename;


			// Dump database (data only) into SQL queries

			try
			{
				db($extensionId)->connect(pref('[Loop]/database/host'), pref('[Loop]/database/root/login'),  pref('[Loop]/database/root/password'), pref('[' . $extensionId . ']/database/name'));
				db($extensionId)->dumpData($pathToFile);
			}
			catch(\Exception $e)
			{
				continue;
			}


			// Remove empty backups

			if (filesize($pathToFile) == 0)
			{
				unlink($pathToFile);
			}
		}
	}
	
	
	/**
	 *
	 */
	
	public function forge($exportBackup = true, $extensionsIds = null)
	{
		// Make sure we have extensions

		if (empty($extensionsIds) === true)
		{
			$extensionsIds = app()->getExtensionManager()->getExtensionsIds();
		}
		

		// Get extensions that are shared

		$allExtensionsIds = app()->getExtensionManager()->getExtensionsIds();
		$nbExtensions = count($allExtensionsIds);
		$sharedExtensionsIds = array();

		for ($i = 0; $i < $nbExtensions; $i++)
		{
			$extensionId = $allExtensionsIds[$i];

			if (pref('[' . $extensionId . ']/database/isShared') == true)
			{
				$sharedExtensionsIds[] = $extensionId;

				if (isset($extensionsIds[$i]) === true)
				{
					unset($extensionsIds[$i]);
				}
			}
		}


		// Create a backup if needed
		
		if ($exportBackup == true)
		{
			$this->export($extensionsIds);
		}


		// Forge non-shared extensions

		foreach ($extensionsIds as $extensionId)
		{
			//

			$models = array();
			$namespaces = array();

			$namespace = str_replace('-', '\\', $extensionId);

			$pathToExtension = app()->getPathHelper()->convertExtensionIdToPath($extensionId) . 'Model/';
			$models[$pathToExtension] = app()->getModelManager()->getModels($namespace, $pathToExtension);
			$namespaces[$pathToExtension] = $namespace;


			// List all shared objects and prepare them with good database connections

			foreach ($sharedExtensionsIds as $sharedExtensionId)
			{
				$pathToSharedExtension = app()->getPathHelper()->convertExtensionIdToPath($sharedExtensionId) . 'Model/';

				$namespaceShared = str_replace('-', '\\', $sharedExtensionId);

				$models[$pathToSharedExtension] = app()->getModelManager()->getModels($namespaceShared, $pathToSharedExtension);
				$namespaces[$pathToSharedExtension] = $namespaceShared;
			}


			// Remove all tables

			$databaseDriver = db($extensionId);		
			$databaseDriver->connect(pref('[Loop]/database/host'), pref('[Loop]/database/root/login'),  pref('[Loop]/database/root/password'));


			// Drop the database
				
			$query = 'DROP DATABASE IF EXISTS `%1`';

			$queryVariables = array
			(
				pref('[' . $extensionId . ']/database/name')
			);
				
			$databaseDriver->executeQuery($query, $queryVariables);


			// Instanciate each classes and forge it

			foreach ($models as $path => $classNames) 
			{
				foreach ($classNames as $className)
				{

					// Create the object as long as it's not abstract
					
					require_once($path . $className . '.php');

					

					// Does the class exist?

					if (class_exists($className) == false)
					{
						
						$className = $namespaces[$path]  . '\\' . $className;

					}


					// Is it abstract?
						
					$reflection = new \ReflectionClass($className);
						
					
					if ($reflection->isAbstract() == true)
					{
						continue;
					}

					$object = new $className();
					
					if (pref('[' . $object->extensionId . ']/database/isShared') == true)
					{
						$object->setDatabaseDriver($databaseDriver);
					}
					
					// Create tables and views for the object
						
					$object->forge();
				}
			}
		}
	}


	/**
	 *
	 */

	public function getLastDateOfBackup($extensionId)
	{
		// Get all backups

		$path = path
		(
			array
			(
				app()->getPathHelper()->convertExtensionIdToPath($extensionId),
				'Data',
				'Backup'
			)
		);

		$pathToFiles = app()->getFileHelper()->listFiles($path, 'Backup_*.sql');


		// Find the latest one

		$pathToFile = null;
		$latestPathToFile = null;
		$latestTime = null;
		
		foreach ($pathToFiles as $pathToFile)
		{
			if
			(
				(is_file($pathToFile) == false) ||
				(filemtime($pathToFile) <= $latestTime)
			)
			{
				continue;
			}
			
			$latestTime = filemtime($pathToFile);
			$latestPathToFile = $pathToFile;
		}


		// Have we found one?
		
		$dateOfBackup = null;

		if (empty($latestPathToFile) === false)
		{
			$filename = substr($pathToFile, strlen($path));
			$dateOfBackup = substr($filename, strlen('Backup_'));
			$dateOfBackup = substr($dateOfBackup, 0, strpos($dateOfBackup, '.sql'));
		}


		return $dateOfBackup;
	}

	
	/**
	 *
	 */
	
	public function import($dateOfBackup, $extensionsIds = null)
	{
		// Make sure we have extensions

		if (empty($extensionsIds) === true)
		{
			$extensionsIds = app()->getExtensionManager()->getExtensionsIds();
		}


		// Install each extension

		foreach ($extensionsIds as $extensionId)
		{
			// This is where the backup is

			$pathToBackup = path
			(
				array
				(
					app()->getPathHelper()->convertExtensionIdToPath($extensionId),
					'Data',
					'Backup'
				)
			);

			$pathToBackup .= 'Backup_' . $dateOfBackup . '.sql';


			// Does the backup exist?
		
			if (file_exists($pathToBackup) == false)
			{
				continue;
			}
			
			
			// Open it!
			
			$fileContent = file_get_contents($pathToBackup);
			$queries = explode(";\n", $fileContent);
			
			
			// Execute SQL queries
			
			db($extensionId)->executeQueries($queries);
		}
	}
	
	
	/**
	 * Installs data required by the application
	 */
	
	public function install($extensionsIds = null)
	{
		// Make sure we have extensions

		if (empty($extensionsIds) === true)
		{
			$extensionsIds = app()->getExtensionManager()->getExtensionsIds();
		}
		

		// Install each extension

		foreach ($extensionsIds as $extensionId)
		{
			// Get the latest date of backup

			$lastDateOfBackup = $this->getLastDateOfBackup($extensionId);


			// Import it
			
			if (is_null($lastDateOfBackup) === false)
			{
				$this->import($lastDateOfBackup, array($extensionId));
			}
		}
	}

	
	/**
	 * Starts maintenance mode
	 *
	 * @param	string	$maintenanceKey	The key used to lock maintenance
	 *
	 * @return	string					The maintenance key
	 */
	
	public function startMaintenance($maintenanceKey = null)
	{
		// Is the application already under maintenance?
		
		$pathToFile = path(array(PATH_TEMPORARY));
		$pathToFile .= 'Maintenance.txt';
		
		if (file_exists($pathToFile) == true)
		{
			e(EXCEPTION_APPLICATION_ALREADY_UNDER_MAINTENANCE);			
		}
		
		
		// Do we have a maintenance key?
		
		if (empty($maintenanceKey) == true)
		{
			$maintenanceKey = app()->getStringHelper()->generateUniqueKey();
		}
		
		
		// Put it under maintenance
		
		file_put_contents($pathToFile, $maintenanceKey);
		
		
		return $maintenanceKey;
	}
	
	
	/**
	 * Stops maintenance mode
	 *
	 * @param	string	$maintenanceKey	The key used to lock maintenance
	 */
	
	public function stopMaintenance($maintenanceKey)
	{
		// Is the application under maintenance?
		
		$pathToFile = path(array(PATH_TEMPORARY));
		$pathToFile .= 'Maintenance.txt';
		
		if (file_exists($pathToFile) == false)
		{
			e(EXCEPTION_APPLICATION_NOT_UNDER_MAINTENANCE);
		}
		
		
		// Get the maintenance key
		
		$realMaintenanceKey = file_get_contents($pathToFile);
		
		
		// Can we stop maintenance?
		
		if ($realMaintenanceKey != $maintenanceKey)
		{
			e(EXCEPTION_APPLICATION_MAINTENANCE_KEY_NOT_VALID);
		}
		
		
		// Stop maintenance
		
		unlink($pathToFile);
	}
	
	
	/**
	 * Runs all automatic tests
	 *
	 * @param	string	$testName	The name of the test that should be run (null by default, runs all tests)
	 * @param	bool	$fullTest	True to perform a full test, false otherwise
	 *
	 * @return	array				An array that contains a report of the test
	 */
	
	public function test($testName = null, $fullTest = true)
	{
		// Run tests
		
		$result = app()->getTestManager()->run($testName, $fullTest);
		
		
		return $result;
	}
}

?>
